﻿/*********************************************************************************
 *Author:         OnClick
 *Version:        1.0
 *UnityVersion:   2018.3.1f1
 *Date:           2019-03-26
 *Description:    IFramework
 *History:        2018.11--
*********************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace IFramework
{
    public class WSServerToken : IDisposable
    {
        private Encoding encoding = Encoding.UTF8;
        private TcpServerToken serverToken = null;
        private List<ConnectionInfo> ConnectionPool = null;
        private Timer threadingTimer = null;
        private int timeout = 1000 * 60 * 6;
        private object lockobject = new object();

        public SockDel.OnReceieve onReceive { get; set; }
        public SockDel.OnAccept onAccept { get; set; }
        public SockDel.OnDisConnect onDisconnect { get; set; }
        public SockDel.OnSendCallBack onSendCallBack { get; set; }

        public WSServerToken(int maxConnections = 32, int bufferSize = 4096)
        {
            ConnectionPool = new List<ConnectionInfo>(maxConnections);
            serverToken = new TcpServerToken(maxConnections, bufferSize);
            serverToken.onAcccept += new SockDel.OnAccept(AcceptedCallBack);
            serverToken.onDisConnect += new SockDel.OnDisConnect(DisconnectCallBack);
            serverToken.onReceive += new SockDel.OnReceieve(ReceiveCallBack);
            serverToken.onSendCallBack += new SockDel.OnSendCallBack(SendCallBack);
            threadingTimer = new Timer(new TimerCallback(TimingEvent), null, -1, -1);
        }
        public void Dispose()
        {
            threadingTimer.Dispose();
        }
        private void TimingEvent(object obj)
        {
            lock (lockobject)
            {
                var items = ConnectionPool.FindAll(x => DateTime.Now.Subtract(x.ConnectedTime).TotalMilliseconds >= timeout);
                foreach (var node in items)
                {
                    CloseAndRemove(node);
                }
            }
        }

        public bool Start(int port, string ip = "0.0.0.0")
        {
            bool isOk = serverToken.Start(port, ip);
            if (isOk)
            {
                threadingTimer.Change(timeout / 2, timeout);
            }
            return isOk;
        }
        public void Stop()
        {
            threadingTimer.Change(-1, -1);
            lock (lockobject)
            {
                foreach (var node in ConnectionPool)
                {
                    CloseAndRemove(node);
                }
            }
        }
        public void Close(SocketToken sToken)
        {
            serverToken.Close(sToken);
        }

        public bool Send(SocketToken sToken, string content, bool waiting = true)
        {
            byte[] buffer = new ServerPackage().GetBytes(content);
            return serverToken.SendAsync(sToken, buffer, 0, buffer.Length, waiting);
        }
        public bool Send(SocketToken token, byte[] buff, int offset, int len, bool waiting = true)
        {
            return serverToken.SendAsync(token, buff, offset, len, waiting);
        }

        private void AcceptedCallBack(SocketToken sToken) { }
        private void DisconnectCallBack(SocketToken sToken)
        {
            ConnectionPool.Remove(new ConnectionInfo() { sToken = sToken });
            if (onDisconnect != null) onDisconnect(sToken);
        }

        private void ReceiveCallBack(SocketToken token, byte[] buff, int offset, int len)
        {
            var connection = ConnectionPool.Find(x => x.sToken.TokenId == token.TokenId);
            if (connection == null)
            {
                connection = new ConnectionInfo() { sToken = token };
                ConnectionPool.Add(connection);
            }
            if (connection.IsHandShaked == false)
            {
                var serverPackage = new ServerPackage();
                var access = serverPackage.GetHandshakePackage(new BufferSegment(buff, offset, len));
                connection.IsHandShaked = access.IsHandShaked;
                if (connection.IsHandShaked == false)
                {
                    CloseAndRemove(connection);
                    return;
                }
                connection.ConnectedTime = DateTime.Now;

                string rsp = serverPackage.RspAcceptPackage(access);

                byte[] buffer = encoding.GetBytes(rsp);
                serverToken.SendAsync(token, buffer, 0, buffer.Length);

                connection.accessInfo = access;
                connection.IsHandShaked = true;

                if (onAccept != null) onAccept(token);
            }
            else
            {
                ClientPackage packet = new ClientPackage();
                packet.DecodingFromBytes(new BufferSegment(buff, offset, len), true);
                if (packet.OpCode == 0x01)
                {
                    //if (OnReceived != null)
                    //    OnReceived(session.sToken, encoding.GetString(packet.Data.buffer,
                    //    packet.Data.offset, packet.Data.Len));
                }
                else if (packet.OpCode == 0x08)
                {
                    CloseAndRemove(connection);
                    return;
                }
                else
                {
                    if (onReceive != null)
                        onReceive(token, packet.Data.Buffer,
                        packet.Data.Offset, packet.Data.Len);

                }
            }
        }

        private void SendCallBack(SocketToken token, byte[] buff, int offset, int len)
        {
            if (onSendCallBack != null)
            {
                onSendCallBack(token, buff, offset, len);
            }
        }

        private void CloseAndRemove(ConnectionInfo connection)
        {
            bool isOk = Remove(connection);
            if (isOk)
            {
                serverToken.Close(connection.sToken);
            }
        }

        private bool Remove(ConnectionInfo info)
        {
            lock (lockobject)
            {
                return ConnectionPool.Remove(info);
            }
        }
        private bool Remove(SocketToken sToken)
        {
            lock (lockobject)
            {
                return ConnectionPool.RemoveAll(x => x.sToken.TokenId == sToken.TokenId) > 0;
            }
        }

        internal class ConnectionInfo : IComparable<SocketToken>
        {
            public SocketToken sToken { get; set; }

            public bool IsHandShaked { get; set; }

            public AccessInfo accessInfo { get; set; }
            public DateTime ConnectedTime { get; set; }/* = DateTime.MinValue;*/
            public int CompareTo(SocketToken info)
            {
                return sToken.TokenId - info.TokenId;
            }
        }
    }
}
